package com.zgx.plus.security;

import cn.hutool.json.JSONUtil;
import com.zgx.plus.common.response.R;
import com.zgx.plus.common.utils.ServletUtils;
import com.zgx.plus.security.model.LoginUser;
import com.zgx.plus.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.Objects;

/**
 * spring security配置
 */
@Slf4j
@EnableWebSecurity
@SpringBootConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {

    @Autowired
    private TokenService tokenService;
    @Autowired
    @Lazy
    private UserDetailsService userDetailsService;

    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     */
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    @Lazy
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                //基于token,不需要csrf
                .csrf().disable()
                //禁用HTTP响应标头
                .headers().cacheControl().disable().frameOptions().disable().and()
                //基于token,不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                //    跨域配置
//                                            .cors().configurationSource(corsConfigurationSource()).and()
//                                            .userDetailsService(userDetailsService())
                //设置权限
                .authorizeRequests(authorize -> authorize
                        //对于登录login 注册register 验证码captchaImage 允许匿名访问
                        .antMatchers("/login", "/register", "/captchaImage").permitAll()
                        //静态资源，可匿名访问
                        .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                        .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                        //其他地址的访问均需验证权限
                        .anyRequest().authenticated()
                )
                //处理退出登录
                .logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {
                    //todo...清空token及登录信息    记录退出日志
                    ServletUtils.renderString(response, JSONUtil.toJsonStr(R.ok()));
                }).and()
                //处理认证失败
                .exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
                    log.error("=authException={}", authException.getMessage(), authException);
                    String authMsg = MessageFormat.format("请求访问：{0}，认证失败，无法访问系统资源", request.getRequestURI());
                    ServletUtils.renderString(response, JSONUtil.toJsonStr(R.fail(HttpStatus.UNAUTHORIZED.value(), authMsg)));
                })
                //处理鉴权失败
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    log.error("=accessDeniedException={}", accessDeniedException.getMessage(), accessDeniedException);
                    String accessMsg = MessageFormat.format("请求访问：{0}，鉴权失败，无法访问系统资源", request.getRequestURI());
                    ServletUtils.renderString(response, JSONUtil.toJsonStr(R.fail(HttpStatus.FORBIDDEN.value(), accessMsg)));
                }).and()
                //    用户认证
                .addFilterBefore((request, response, chain) -> {
                    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                    LoginUser loginUser = tokenService.parseToken(httpServletRequest);
                    if (Objects.nonNull(loginUser) && Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
                        tokenService.verifyToken(loginUser);
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    }
                    chain.doFilter(request, response);
                }, UsernamePasswordAuthenticationFilter.class)
                .userDetailsService(userDetailsService)
                .build();
    }
}
